package db

import (
	"context"
	"fmt"
	"log"
	"os"
	"poker/env"
	"strings"
	"time"

	"github.com/nlh1996/utils"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// Mgo .
type Mgo struct {
	client *mongo.Client
	db     *mongo.Database
}

var (
	mgo         *Mgo
	logger      *log.Logger
	mgoEnv      = env.GlobalData.Server
	url         = "mongodb://" + mgoEnv.MgoAddress + ":" + utils.IntToString(mgoEnv.MgoPort)
	logFilePath = "./mongo_errors.log"
)

// 初始化日志记录器
func initLogger() {
	logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Failed to open log file: %v", err)
	}

	logger = log.New(logFile, "[MongoDB Error] ", log.LstdFlags|log.Lmicroseconds)
}

// InitMgo 数据库连接.
func InitMgo() {
	initLogger()
	mgo = &Mgo{}
	var err error

	// 设置 MongoDB 客户端选项
	opt := options.Client().ApplyURI(url)
	if !strings.Contains(url, "localhost") {
		opt.SetAuth(options.Credential{
			Username: mgoEnv.MgoUser,
			Password: mgoEnv.MgoPassword,
		})
	}
	opt.SetMaxPoolSize(200)

	// 连接到 MongoDB
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	mgo.client, err = mongo.Connect(ctx, opt)
	if err != nil {
		log.Fatalf("Failed to connect to MongoDB: %v", err)
	}

	// 检查连接
	err = mgo.client.Ping(ctx, nil)
	if err != nil {
		log.Fatalf("Failed to ping MongoDB: %v", err)
	}
	log.Println("Connected to MongoDB!")

	// 设置默认数据库
	mgo.db = SetDB(mgoEnv.DBName)
}

// SetDB .
func SetDB(str string) *mongo.Database {
	if mgo.client == nil {
		return nil
	}
	return mgo.client.Database(str)
}

// GetDB .
func GetDB() *mongo.Database {
	if mgo.db != nil {
		return mgo.db
	}
	return nil
}

// Col .
func Col(col string) *mongo.Collection {
	if mgo.db != nil {
		return mgo.db.Collection(col)
	}
	return nil
}

// checkErr 捕获 MongoDB 操作中的错误，并记录到本地日志文件中
func checkErr(col string, funcName string, filter interface{}, err error) {
	// 格式化错误信息
	errMsg := fmt.Sprintf("%s failed! DB: %s, Collection: %s, filter: %s, ErrInfo: %v", funcName, mgoEnv.DBName, col, filter, err)
	logger.Println(errMsg)
}

// InsertOne .
func InsertOne(col string, data interface{}) error {
	// 使用上下文控制超时
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_, err := Col(col).InsertOne(ctx, data)
	if err != nil {
		checkErr(col, "InsertOne", nil, err)
	}
	return err
}

// InsertMany .
func InsertMany(col string, data []interface{}) error {
	_, err := Col(col).InsertMany(utils.GetCtx(), data)
	if err != nil {
		checkErr(col, "InsertMany", nil, err)
	}
	return err
}

// FindOne .
func FindOne(col string, filter interface{}, obj interface{}, opts ...*options.FindOneOptions) error {
	err := Col(col).FindOne(utils.GetCtx(), filter, opts...).Decode(obj)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			return err
		}
		checkErr(col, "FindOne", filter, err)
	}
	return err
}

// Find .
func Find(col string, filter interface{}, res interface{}, opts ...*options.FindOptions) error {
	ctx, cancel := context.WithTimeout(utils.GetCtx(), 10*time.Second)
	defer cancel()

	cursor, err := Col(col).Find(ctx, filter, opts...)
	if err != nil {
		checkErr(col, "Find", filter, err)
		return err
	}
	defer cursor.Close(ctx)

	if err := cursor.All(ctx, res); err != nil {
		checkErr(col, "Find.All", filter, err)
		return err
	}
	return nil
}

// FindOneAndReplace .
func FindOneAndReplace(col string, filter interface{}, data interface{}) (interface{}, error) {
	var result interface{}
	err := Col(col).FindOneAndReplace(utils.GetCtx(), filter, data).Decode(&result)
	if err != nil {
		checkErr(col, "FindOneAndReplace", filter, err)
	}
	return result, err
}

// DeleteOne .
func DeleteOne(col string, filter interface{}) error {
	_, err := Col(col).DeleteOne(utils.GetCtx(), filter)
	if err != nil {
		checkErr(col, "DeleteOne", filter, err)
	}
	return err
}

// UpdateOne .
func UpdateOne(col string, filter interface{}, update interface{}) error {
	// 设置 upsert 选项
	opts := options.Update().SetUpsert(true)
	_, err := Col(col).UpdateOne(utils.GetCtx(), filter, update, opts)
	if err != nil {
		checkErr(col, "UpdateOne", filter, err)
	}
	return err
}

// ReplaceOne .
func ReplaceOne(col string, filter interface{}, data interface{}) error {
	_, err := Col(col).ReplaceOne(utils.GetCtx(), filter, data)
	if err != nil {
		checkErr(col, "ReplaceOne", filter, err)
	}
	return err
}

// Count .
func Count(col string, filter interface{}) (int64, error) {
	return Col(col).CountDocuments(utils.GetCtx(), filter)
}
